
from formula import *
from random import randrange, random, shuffle
import numpy as np
np.seterr(all='raise')

class Gene(object):
	def __init__(self, signal, initialFormula=True):
		self._initFormulasList()
		self.signal = np.array(signal)
		self.meanSignal = self.signal.mean()
		self.lenSignal = len(signal)
		self.x = np.arange(1, self.lenSignal+1)
		#self.formula = XFormula()
		if initialFormula:
			self.formula = ConstanteFormula(self, self.meanSignal)
			self.formula.setParent(self)
			self.maxDistanceToSignal = self.getDistanceToSignal()
			self.lastDistanceToSignal = self.maxDistanceToSignal

	def _initFormulasList(self):
		self.formulas = []
		self.hashFormulas = {}
		self.cteFormulas = []
		self.hashCteFormulas = {}

	def setFormula(self, formula):
		#TODO clean formula
		self.formula = formula
		self.formula.setParent(self)
		self.maxDistanceToSignal = self.getDistanceToSignal()
		self.lastDistanceToSignal = self.maxDistanceToSignal

	def isNull(self):
		return False

	def generateFormula(self, baseFormula):
		randomFunction = lambdas[randrange(0, len(lambdas))]
		self.generateFormulaFromFunction(baseFormula, randomFunction)

	def generateFormulaFromFunction(self, baseFormula, randomFunction):
		initialParentBase = baseFormula.parentFormula
		params = self.generateParamsOfNewFunction(baseFormula, randomFunction)
		newFormula = Formula(self, randomFunction, params)
		newFormula.setParent(initialParentBase)
		initialParentBase.replaceChild(baseFormula, newFormula)
		assert(newFormula.parentFormula==initialParentBase)
		assert(baseFormula in newFormula.childFormulas)
		assert(baseFormula.parentFormula==newFormula)
		assert(type(newFormula.parentFormula) in (ImageGene, Gene, NullFormula) or not baseFormula in newFormula.parentFormula.childFormulas)
		assert(baseFormula.childFormulas == None or not newFormula in baseFormula.childFormulas)
		assert(baseFormula.childFormulas == None or not baseFormula in baseFormula.childFormulas)
		assert(not newFormula in newFormula.childFormulas)
		#baseFormula.insertBack(newFormula)

	def generateParamsOfNewFunction(self, baseFormula, randomFunction):
		nParams = randomFunction.getNParams()
		params = [baseFormula]

		if not baseFormula in (hashLambdas['pow'], hashLambdas['absPow']):
			for x in xrange(nParams-1):
				newSimpleFormula = self.generateCteOrXFormula()
				params.append(newSimpleFormula)
				newSimpleFormula.setParent(baseFormula)
			shuffle(params)
		else:
			params.append(self.generateCteFormulaFromAmplitude())
		return params

	def generateCteOrXFormula(self):
		if(random()<0.5):
			return XFormula(self)
		else:
			return self.generateCteFormula()

	def generateCteFormula(self):
		if(random()<0.5):
			return self.generateCteFormulaFromAmplitude()
		else:
			return self.generateCteFormulaFromLengthSignal()

	def generateCteFormulaFromAmplitude(self):
		cte = (random() - 0.5)*self.meanSignal
		return ConstanteFormula(self, cte)

	def generateCteFormulaFromLengthSignal(self):
		cte = random() * self.lenSignal
		return ConstanteFormula(self, cte)

	def register(self, formula):
		if not formula.isNull():
			if(type(formula) == ConstanteFormula):
				index = len(self.cteFormulas)
				self.cteFormulas.append(formula)
				self.hashCteFormulas[formula] = index
			index = len(self.formulas)
			self.formulas.append(formula)
			self.hashFormulas[formula] = index

	def restartRegistration(self):
		self._initFormulasList()
		self.formula.restartRegistration(self)

	def delete(self, formula):
		if type(formula) == ConstanteFormula:
			del self.cteFormulas[self.hashCteFormulas[formula]]
			del self.hashCteFormulas[formula]
		del self.formulas[self.hashFormulas[formula]]
		del self.hashFormulas[formula]

	def deleteWithChild(self, formula):
		self.delete(formula)
		if(formula.childFormulas != None):
			for child in formula.childFormulas:
				self.deleteWithChild(child)

	def replaceChild(self, old, new):
		self.formula = new

	def getDistanceToSignal(self):
		try:
			#vectorizedFormula = np.vectorize(self.formula)
			#estimatedSignal = map(self.formula, np.arange(self.lenSignal))
			#estimatedSignal = vectorizedFormula(np.arange(1, self.lenSignal+1))
			estimatedSignal = self.getEstimatedSignal()
			self.lastDistanceToSignal = np.linalg.norm(self.signal - estimatedSignal)
			#self.trueRelativeDistance = 1 - estimatedSignal.var()/self.signal.var()
			return self.lastDistanceToSignal
		except (ZeroDivisionError, TypeError, ValueError, OverflowError, FloatingPointError):
			return -1

	def getEstimatedSignal(self):
		#print self
		vectorizedFormula = np.vectorize(self.formula)
		#print "vec ok"
		estimatedSignal = vectorizedFormula(self.x)
		#print "est ok" # des fois je n'arrive pas la et rentre dans une boucle infinie
		return estimatedSignal

	def getRelativeDistanceToSignal(self):
		if self.lastDistanceToSignal < 0:
			return -1
		#print "self.maxDistanceToSignal:", self.maxDistanceToSignal
		return self.lastDistanceToSignal/self.maxDistanceToSignal
		#return self.trueRelativeDistance

	def change(self):
		if len(self.cteFormulas) > 0:
			index = randrange(0, len(self.cteFormulas))
			shift = (random() - 0.5)*sqrt(abs(self.cteFormulas[index].constante))
			self.cteFormulas[index].setConstante(self.cteFormulas[index].constante + shift)
		return self

	def easyMutate(self):
		if len(self.formulas) > 0:
			index = randrange(0, len(self.formulas))
			self.generateFormula(self.formulas[index])
		return self

	def advanceMutate(self):
		x = XFormula(self)
		randomFunction = lambdas[randrange(0, len(lambdas))]
		params = self.generateParamsOfNewFunction(x, randomFunction)
		randomFormula = Formula(self, randomFunction, params)
		c1 = self.generateCteFormulaFromLengthSignal()
		#c2 = self.generateCteFormulaFromLengthSignal()
		#if c1.constante > c2.constante:
			#c1, c2 = c2, c1
		inf = Formula(self, hashLambdas["inf"], [randomFormula, c1])
		#identity = Formula(self, hashLambdas["inf"], [randomFormula, c1, c2])
		plus = Formula(self, hashLambdas["plus"], [self.formula, inf])
		plus.setParent(self)
		self.formula = plus
		return self

	def dualMutation(self, other):
		#print "ALERT ALERT this function is not ready\n"*10
		other = other.clone()
		indexToBeReplace = randrange(0, len(self.formulas))
		selfFormula = self.formulas[indexToBeReplace]
		indexReplacement = randrange(0, len(other.formulas))
		otherFormula = other.formulas[indexReplacement]
		selfFormula.parentFormula.replaceChild(selfFormula, otherFormula)
		otherFormula.setParent(selfFormula.parentFormula)
		self.restartRegistration()
		copy = self.clone()
		assert(len(self.formulas) == len(copy.formulas))
		#try:
		#except AssertionError:
			#print len(self.formulas), len(copy.formulas)
			#print "self.formulas:", self.formulas
			#print "copy.formulas:", copy.formulas
			#print "self:", self
			#print "copy:", copy
			#print "[self.formula]:", [self.formula]
			#print "[copy.formula]:", [copy.formula]
			#print "copy == copy.formula.gene:", copy == copy.formula.gene
			#print "self == self.formula.gene:", self == self.formula.gene
			#assert(len(self.formulas) == len(copy.formulas))

		#self.delete(selfFormula)
		return self

	def clone(self):
		copy = Gene(self.signal, False)
		#copy.delete(copy.formula)
		copy.setFormula(self.formula.clone(copy))
		copy.maxDistanceToSignal = self.maxDistanceToSignal
		copy.lastDistanceToSignal = self.lastDistanceToSignal
		assert(len(self.formulas) == len(copy.formulas))
		#try:
		#except AssertionError:
			#print len(self.formulas), len(copy.formulas)
			#print "self.formulas:", self.formulas
			#print "copy.formulas:", copy.formulas
			#print "self:", self
			#print "copy:", copy
			#print "[self.formula]:", [self.formula]
			#print "[copy.formula]:", [copy.formula]
			#assert(len(self.formulas) == len(copy.formulas))
		for old in self.formulas:
			assert(not old in copy.formulas)
		return copy

	def __str__(self):
		return self.formula.__str__()

	def __repr__(self):
		return self.formula.__str__()


class ImageGene(Gene):
	def __init__(self, x, y, signalColorComp, initialFormula=True):
		#super(ImageGene, self).__init__(self, signalColorComp, InitialFormula)
		self._initFormulasList()
		self.x = x
		self.y = y
		self.signal = np.array(signalColorComp)
		self.meanSignal = self.signal.mean()
		self.lenSignal = len(self.signal)
		if initialFormula:
			self.formula = ConstanteFormula(self, self.meanSignal)
			self.formula.setParent(self)
			self.maxDistanceToSignal = self.getDistanceToSignal()
			self.lastDistanceToSignal = self.maxDistanceToSignal

	def generateCteOrXFormula(self):
		alea = random()
		if(alea<0.3):
			return XFormula(self)
		elif(alea<0.6):
			return YFormula(self)
		else:
			return self.generateCteFormula()

	def getEstimatedSignal(self):
		vectorizedFormula = np.vectorize(self.formula)
		#print "vec ok"
		estimatedSignal = vectorizedFormula(self.x, self.y)
		#print "est ok"
		return estimatedSignal

	def clone(self):
		copy = ImageGene(self.x, self.y, self.signal, False)
		copy.setFormula(self.formula.clone(copy))
		copy.maxDistanceToSignal = self.maxDistanceToSignal
		copy.lastDistanceToSignal = self.lastDistanceToSignal
		assert(len(self.formulas) == len(copy.formulas))
		for old in self.formulas:
			assert(not old in copy.formulas)
		return copy



if __name__ == '__main__':
	signal = [1,12,1,13,2,12,1,5]
	gene = Gene(signal)
	print "Initial gene:"
	print gene
	print "\ngene.easyMutate():"
	gene.easyMutate()
	print gene
	print "\ngene.advanceMutate():"
	gene.advanceMutate()
	print gene
	print "\ngene.easyMutate():"
	gene.easyMutate()
	print gene
	print "\ngene.easyMutate():"
	gene.easyMutate()
	print gene
	print "\ngene.change():"
	gene.change()
	print gene
	clone = gene.clone()
	print "\nclone"
	print clone
	#print "gene.formulas:", gene.formulas
	#print "clone.formulas:", clone.formulas
	clone.advanceMutate()
	print "\nclone.advanceMutate():"
	print clone
	print "\ngene:"
	print gene
	print "\ndistance gene:", gene.getDistanceToSignal()
	print "\ndistance clone:", clone.getDistanceToSignal()

	print "\n===================================================="
	print "\ngene:"
	print gene
	gene2 = Gene(signal)
	gene2.easyMutate()
	gene2.advanceMutate()
	gene2.easyMutate()
	gene2.easyMutate()
	print "\ngene2:"
	print gene2
	gene2.dualMutation(gene)
	print "\ngene2.dualMutation(gene):"
	print gene2
	print "\n=========================================="
	print "\n------------------------------------------"
	print "\n=========================================="
	for x in xrange(5):
		gene2 = gene2.clone()
		gene2.easyMutate()
